home *** CD-ROM | disk | FTP | other *** search
Text File | 1997-05-22 | 16.1 KB | 670 lines | [TEXT/MPCC] |
- //
- // File: TestFunctions.c
- //
- // Contains: Speech recognition support for QuickTime VR movies.
- //
- // Written by: Tim Monroe
- //
- // Copyright: © 1994-1996 by Apple Computer, Inc., all rights reserved.
- //
- // Change History (most recent first):
- //
- // <1> 12/05/96 rtm ported earlier speech recognition support functions to VRShell
- //
- //
- // TO DO:
- // *get transition swing working, for smoother transitions
- // *on a mousedown event in QTVR window, stop spinning immediately??
- // *implement node navigation
- // *presumably, spinning should be on a per-instance basis
-
-
- // header files
- #include "TestFunctions.h"
- #include "LMSpeech.h" // refcons of language model elements
-
- #include "MacFramework.h"
- #include "QTVRUtilities.h"
-
- // system headers
- #include <Windows.h>
- #include <Resources.h>
- #include <GestaltEqu.h>
- #include <TextUtils.h>
- #include <FixMath.h>
- #include <Speech.h>
-
- extern Boolean gHasSpeechRec;
-
- // declare global variables
- SRRecognitionSystem gSystem;
- SRRecognizer gRecognizer;
- SRLanguageModel gVRLM;
- MyTMTask gTMTaskRec;
- TimerUPP gTimerTaskUPP;
- Boolean gDoSpeechTask; // is a speech-initiated periodical active?
-
- // local function prototypes
- pascal void MySpinTask (MyTMTaskPtr theTaskPtr);
- void InstallSpeechFeedbackRoutine (QTVRInstance theInstance);
- pascal void SpeechFeedbackRoutine (QTVRInstance theInstance, QTVRInterceptPtr theMsg, SInt32 refcon, Boolean *cancel);
-
-
- /////
- //
- // SpeechInit
- // initialize speech recognition, if it's available
- //
- /////
-
- void SpeechInit (void)
- {
- OSErr myErr;
- long myResponse;
- unsigned long myParam;
-
- myErr = Gestalt(gestaltSpeechRecognitionVersion, &myResponse);
- // version must be at least 1.5.0 to support SRM API used here
- if (!myErr)
- if (myResponse >= 0x00000150)
- gHasSpeechRec = true;
-
- if (!gHasSpeechRec)
- return;
-
- // open a recognition system
- if (!myErr)
- myErr = SROpenRecognitionSystem(&gSystem, kSRDefaultRecognitionSystemID);
-
- // set recognition system properties
- // we want the user-selected feedback and listening modes
- if (!myErr) {
- short myModes = kSRHasFeedbackHasListenModes;
- myErr = SRSetProperty(gSystem, kSRFeedbackAndListeningModes, &myModes, sizeof(myModes));
- }
-
- // create a recognizer with default speech source
- if (!myErr)
- myErr = SRNewRecognizer(gSystem, &gRecognizer, kSRDefaultSpeechSource);
-
- // set recognizer properties
- if (!myErr) {
- // we'd like *our* top-level LM to be the only one active;
- Boolean myBlock = true;
- myErr = SRSetProperty(gRecognizer, kSRBlockBackground, &myBlock, sizeof(myBlock));
-
- // we want to receive speech-begun and recognition-done Apple events
- myParam = kSRNotifyRecognitionBeginning | kSRNotifyRecognitionDone;
- myErr = SRSetProperty(gRecognizer, kSRNotificationParam, &myParam, sizeof(myParam));
- }
-
- // install Apple event handlers
- if (!myErr) {
- myErr = AEInstallEventHandler(kAESpeechSuite, kAESpeechDetected, NewAEEventHandlerProc(HandleSpeechBegunAppleEvent), 0, false);
- myErr = AEInstallEventHandler(kAESpeechSuite, kAESpeechDone, NewAEEventHandlerProc(HandleSpeechDoneAppleEvent), 0, false);
- }
-
- // get our language models
- if (!myErr)
- myErr = ReadLanguageModelsFromResource();
-
- // install initial language model
- if (!myErr)
- myErr = SRSetLanguageModel(gRecognizer, gVRLM);
-
- // have the recognizer start processing sound
- if (!myErr)
- myErr = SRStartListening(gRecognizer);
-
- // allocate our spinning task
- gTimerTaskUPP = NewTimerProc(MySpinTask);
- }
-
-
- /////
- //
- // SpeechStop
- // shut down speech recognition
- //
- /////
-
- void SpeechStop (void)
- {
- //stop any spinning before we exit
- if (IsSpinning()) {
- StopSpinning();
- }
-
- //release any existing language models
- SRReleaseObject(gVRLM);
-
- //shut down speech recognition
- SRStopListening(gRecognizer); // stop processing incoming sound
- SRReleaseObject(gRecognizer); // balance SRNewRecognizer call
- SRCloseRecognitionSystem(gSystem); // balance SROpenRecognitionSystem call
- }
-
-
- /////
- //
- // HandleSpeechBegunAppleEvent
- // handle speech-begun events; currently this does nothing interesting;
- // in the future, we'll use it to adjust our language models according to context
- //
- /////
-
- pascal OSErr HandleSpeechBegunAppleEvent (AppleEvent *theAEevt, AppleEvent* reply, long refcon)
- {
- #pragma unused(reply, refcon)
-
- long myActualSize;
- DescType myActualType;
- OSErr myErr = noErr, recErr = noErr;
- SRRecognizer myRec;
-
- // get status and recognizer
- myErr = AEGetParamPtr(theAEevt, keySRSpeechStatus, typeShortInteger, &myActualType, (Ptr)&recErr, sizeof(recErr), &myActualSize);
- if (!myErr && !recErr) {
- myErr = AEGetParamPtr(theAEevt, keySRRecognizer, typeSRRecognizer, &myActualType, (Ptr)&myRec, sizeof(myRec), &myActualSize);
- }
-
- // better bail if I couldn't get status or recognizer!
- if (myErr)
- if (!myRec)
- return(myErr);
-
- // here is where we would adjust LMs according to context
-
- // now tell the recognizer to continue
- myErr = SRContinueRecognition(myRec);
- return(myErr);
- }
-
-
- /////
- //
- // HandleSpeechDoneAppleEvent
- // handle recognition-done Apple event.
- //
- /////
-
- pascal OSErr HandleSpeechDoneAppleEvent (AppleEvent *theAEevt, AppleEvent* reply, long refcon)
- {
- #pragma unused(reply, refcon)
-
- long myActualSize;
- DescType myActualType;
- OSErr myErr = 0, recErr = 0;
- SRRecognitionResult myRecResult;
- SRLanguageModel myResultLM;
- Size myLen;
- QTVRInstance myInstance;
- long myDir; // the direction we're moving
- long myAmt; // the amount we're moving
- long myCount;
-
- // get recognition result status
- myErr = AEGetParamPtr(theAEevt, keySRSpeechStatus, typeShortInteger, &myActualType, (Ptr)&recErr, sizeof(recErr), &myActualSize);
-
- // get recognition result
- if (!myErr && !recErr)
- myErr = AEGetParamPtr(theAEevt, keySRSpeechResult, typeSRSpeechResult, &myActualType, (Ptr)&myRecResult, sizeof(myRecResult), &myActualSize);
-
- // better bail if I couldn't get the recognition result!
- if (myErr)
- return(myErr);
-
- // get the current movie
- myInstance = GetQTVRInstanceFromFrontWindow();
- if (!myInstance)
- return(invalidMovie);
-
- // extract the language model from the recognition result...
- myLen = sizeof(myResultLM);
- myErr = SRGetProperty(myRecResult, kSRLanguageModelFormat, &myResultLM, &myLen);
-
- if (!myErr) {
- long myRefCon;
- SRLanguageObject myItem1;
- SRLanguageObject myItem2;
- SRPath myPath;
-
- // ...and then get its refcon, so we know which one it is
- myLen = sizeof(myRefCon);
- myErr = SRGetProperty(myResultLM, kSRRefCon, &myRefCon, &myLen);
-
- // at this point, the refcon better be kVRAllCmd; otherwise, bail
- if (myRefCon != kVRAllCmd)
- return(kSRModelMismatch);
-
- // get the one and only item in the top-level language model, a path
- myErr = SRGetIndexedItem(myResultLM, &myPath, 0);
- myLen = sizeof(myRefCon);
- myErr = SRGetProperty(myPath, kSRRefCon, &myRefCon, &myLen);
-
- switch (myRefCon) {
- case kMoveDirAndDeg: // these two parse similarly
- case kMoveDirAndRad:
- myErr = SRGetIndexedItem(myPath, &myItem1, 1); // it's a one-item LM!
- myErr = SRGetIndexedItem(myItem1, &myItem2, 0); // so get the enclosed item
- myLen = sizeof(myRefCon);
- myErr = SRGetProperty(myItem2, kSRRefCon, &myDir, &myLen);
- myErr = SRGetIndexedItem(myPath, &myItem1, 2); // it's a one-item LM!
- myErr = SRGetIndexedItem(myItem1, &myItem2, 0); // so get the enclosed item
- myLen = sizeof(myRefCon);
- myErr = SRGetProperty(myItem2, kSRRefCon, &myAmt, &myLen);
- if (myRefCon == kMoveDirAndDeg)
- GoDirByDegrees(myInstance, myDir, myAmt);
- else
- GoDirByRadians(myInstance, myDir, myAmt);
- break;
- break;
-
- case kMoveToNode:
- break;
-
- case kZoomDir:
- myErr = SRCountItems(myPath, &myCount);
- myErr = SRGetIndexedItem(myPath, &myItem1, myCount - 1); // it's a one-item LM!
- myErr = SRGetIndexedItem(myItem1, &myItem2, 0); // so get the enclosed item
- myLen = sizeof(myRefCon);
- myErr = SRGetProperty(myItem2, kSRRefCon, &myDir, &myLen);
- ZoomInOrOut(myInstance, myDir);
- break;
-
- case kSpinStart:
- myErr = SRGetIndexedItem(myPath, &myItem1, 1); // it's a one-item LM!
- myErr = SRGetIndexedItem(myItem1, &myItem2, 0); // so get the enclosed item
- myLen = sizeof(myRefCon);
- myErr = SRGetProperty(myItem2, kSRRefCon, &myDir, &myLen);
- StartSpinning(myInstance, myDir);
- break;
-
- case kSpinStop:
- StopSpinning();
- break;
-
- default:
- break;
- }
-
- SRReleaseObject(myItem1);
- SRReleaseObject(myItem2);
- SRReleaseObject(myPath);
- }
-
- // release recognition result, since we are done with it
- SRReleaseObject(myRecResult);
- SRReleaseObject(myResultLM);
-
- return(myErr);
- }
-
-
- /////
- //
- // ReadLanguageModelsFromResource
- // get our language model(s); here we read a pre-rolled model from a resource
- //
- /////
-
- OSErr ReadLanguageModelsFromResource (void)
- {
- OSErr myErr = noErr;
- Handle myResourceHandle = nil;
-
- // open the language model resource from the resource fork
- myResourceHandle = GetResource(kLMResourceType, kLMResourceID);
- if (!myResourceHandle)
- return(ResError());
-
- // convert language model resource to a language model
- myErr = SRNewLanguageObjectFromHandle(gSystem, &gVRLM, myResourceHandle);
- return(myErr);
- }
-
-
- /////
- //
- // GoDirByDegrees
- // move a given number of degrees in a given direction
- // return value: TRUE if a movement was made; FALSE if no movement was made
- //
- /////
-
- Boolean GoDirByDegrees (QTVRInstance theInstance, long theDir, long theAmt)
- {
- float myAngle;
- Boolean isMoved = false;
-
- QTVRSetAngularUnits(theInstance, kQTVRDegrees);
- switch (theAmt) { // convert the constant to a number of degrees; yuck!
- case kAng45:
- theAmt = 45.0;
- break;
- case kAng90 :
- theAmt = 90.0;
- break;
- case kAng135:
- theAmt = 135.0;
- break;
- case kAng180:
- theAmt = 180.0;
- break;
- case kAng225:
- theAmt = 225.0;
- break;
- case kAng270:
- theAmt = 270.0;
- break;
- case kAng315:
- theAmt = 315.0;
- break;
- case kAng10:
- theAmt = 10.0;
- break;
- case kAng36:
- theAmt = 36.0;
- break;
- case kUndefinedDegrees:
- theAmt = 5.0;
- break;
- default:
- theAmt = 10.0;
- break;
- }
-
- switch (theDir) {
- case kDirUp:
- myAngle = QTVRGetTiltAngle(theInstance);
- QTVRSetTiltAngle(theInstance, myAngle + theAmt);
- break;
-
- case kDirDown:
- myAngle = QTVRGetTiltAngle(theInstance);
- QTVRSetTiltAngle(theInstance, myAngle - theAmt);
- break;
-
- case kDirLeft:
- myAngle = QTVRGetPanAngle(theInstance);
- QTVRSetPanAngle(theInstance, myAngle + theAmt);
- break;
-
- case kDirRight:
- myAngle = QTVRGetPanAngle(theInstance);
- QTVRSetPanAngle(theInstance, myAngle - theAmt);
- break;
-
- default:
- break;
- }
-
- QTVRUpdate(theInstance, kQTVRStatic);
-
- // determine whether a movement actually occurred
- switch (theDir) {
- case kDirUp:
- case kDirDown:
- isMoved = (myAngle != QTVRGetTiltAngle(theInstance));
- break;
- case kDirLeft:
- case kDirRight:
- isMoved = (myAngle != QTVRGetPanAngle(theInstance));
- break;
- default:
- break;
- }
-
- return(isMoved);
- }
-
-
- /////
- //
- // GoDirByRadians
- // move a given number of radians in a given direction
- // return value: TRUE if a movement was made; FALSE if no movement was made
- //
- /////
-
- Boolean GoDirByRadians (QTVRInstance theInstance, long theDir, long theAmt)
- {
- // convert radians to degrees, then call GoDirByDegrees
- switch (theAmt) {
- case kRad1PiOver4:
- theAmt = kAng45;
- break;
- case kRad2PiOver4:
- theAmt = kAng90;
- break;
- case kRad3PiOver4:
- theAmt = kAng135;
- break;
- case kRad4PiOver4:
- theAmt = kAng180;
- break;
- case kRad5PiOver4:
- theAmt = kAng225;
- break;
- case kRad6PiOver4:
- theAmt = kAng270;
- break;
- case kRad7PiOver4:
- theAmt = kAng315;
- break;
- default:
- theAmt = kAng10;
- break;
- }
-
- return(GoDirByDegrees(theInstance, theDir, theAmt));
- }
-
-
- /////
- //
- // ZoomInOrOut
- // zoom in or out
- //
- /////
-
- void ZoomInOrOut (QTVRInstance theInstance, long theDir)
- {
- float myFloat;
-
- myFloat = QTVRGetFieldOfView(theInstance);
- switch (theDir) {
- case kDirIn:
- myFloat = myFloat / 2.0;
- break;
- case kDirOut:
- myFloat = myFloat * 2.0;
- break;
- default:
- break;
- }
- QTVRSetFieldOfView(theInstance, myFloat);
- QTVRUpdate(theInstance, kQTVRStatic);
- }
-
-
- /////
- //
- // MySpinTask
- // move in the desired direction, then re-prime the timer task
- // (here we just set an app global to alert code in event loop to do the move)
- //
- /////
-
- pascal void MySpinTask (MyTMTaskPtr theTaskPtr)
- {
- gDoSpeechTask = true;
- PrimeTime((QElemPtr)theTaskPtr, theTaskPtr->theSpinDelay);
- }
-
-
- /////
- //
- // DoEventLoopSpinCheck
- // see whether a spin task is active, and respond appropriately
- //
- /////
-
- void DoEventLoopSpinCheck (void)
- {
- if (gDoSpeechTask) {
- if (!GoDirByDegrees(gTMTaskRec.theInstance, gTMTaskRec.theSpinDir, gTMTaskRec.theSpinAmt)) {
- StopSpinning();
- }
- gDoSpeechTask = false;
- }
- }
-
-
- /////
- //
- // IsSpinning
- // is the spinning task installed?
- //
- /////
-
- Boolean IsSpinning (void)
- {
- return(gTMTaskRec.theTMTask.qType && kTMTaskActive);
- }
-
-
- /////
- //
- // StartSpinning
- // start spinning in a given direction
- //
- /////
-
- void StartSpinning (QTVRInstance theInstance, long theDir)
- {
- // first we should check that our task isn't already installed;
- // if it is, remove it (and then continue to install new task)
- if (IsSpinning()) {
- StopSpinning();
- }
-
- // install a Time Manager task that periodically moves a small amount (5 degrees)
- gTMTaskRec.theTMTask.tmAddr = gTimerTaskUPP;
- gTMTaskRec.theTMTask.tmWakeUp = 0;
- gTMTaskRec.theTMTask.tmReserved = 0;
- gTMTaskRec.theInstance = theInstance;
- gTMTaskRec.theSpinDir = theDir;
- gTMTaskRec.theSpinAmt = kUndefinedDegrees;
- gTMTaskRec.theSpinDelay = kSpinMillisecsDelay;
-
- InsXTime((QElemPtr) &gTMTaskRec);
- PrimeTime((QElemPtr) &gTMTaskRec, kSpinMillisecsDelay);
- }
-
-
- /////
- //
- // StopSpinning
- // stop spinning: remove the Time Manager task
- //
- /////
-
- void StopSpinning (void)
- {
- RmvTime((QElemPtr) &gTMTaskRec);
- }
-
-
- /////
- //
- // InstallSpeechFeedbackRoutine
- // Set up QTVR intercept routines to do some speech.
- //
- /////
-
- void InstallSpeechFeedbackRoutine (QTVRInstance theInstance)
- {
- QTVRInterceptUPP theInterceptProc;
-
- theInterceptProc = NewQTVRInterceptProc(SpeechFeedbackRoutine);
-
- //We'll just use the same intercept proc for each intercepted procedure.
- QTVRInstallInterceptProc(theInstance, kQTVRSetPanAngleSelector, theInterceptProc, 0, 0);
- QTVRInstallInterceptProc(theInstance, kQTVRSetTiltAngleSelector, theInterceptProc, 0, 0);
- QTVRInstallInterceptProc(theInstance, kQTVRSetFieldOfViewSelector, theInterceptProc, 0, 0);
- QTVRInstallInterceptProc(theInstance, kQTVRTriggerHotSpotSelector, theInterceptProc, 0, 0);
- }
-
-
- /////
- //
- // SpeechFeedbackRoutine
- //
- /////
-
- #define FloatToFixed(a) ((Fixed)((float)(a) * fixed1))
- #define VRPi (3.1415926535898)
- #define VRDegreesToRadians(a) ((a) * VRPi / 180.0)
- #define VRRadiansToDegrees(a) ((a) * 180.0 / VRPi)
-
- pascal void SpeechFeedbackRoutine (QTVRInstance theInstance, QTVRInterceptPtr theMsg, SInt32 refCon, Boolean *cancel)
- {
- #pragma unused(refCon)
-
- Str255 myCaption;
- Boolean myCancelInterceptedProc = false; // true == do NOT call thru; false == call thru
- float myAngle, *myAnglePtr;
-
- switch (theMsg->selector) {
- case kQTVRSetTiltAngleSelector:
- case kQTVRSetPanAngleSelector:
- case kQTVRSetFieldOfViewSelector:
- myAnglePtr = (float *)theMsg->parameter[0];
- myAngle = *myAnglePtr; //this is always in radians!
- myAngle = VRRadiansToDegrees(myAngle);
- NumToString(Fix2Long(FloatToFixed(myAngle)), myCaption);
- QTVRCallInterceptedProc(theInstance, theMsg);
- SpeakString(myCaption);
- myCancelInterceptedProc = true;
- break;
-
- case kQTVRTriggerHotSpotSelector: // get the hot spot ID and speak it
- NumToString((long)theMsg->parameter[0], myCaption);
- SRSpeakAndDrawText(gRecognizer, &myCaption[1], myCaption[0]);
- break;
-
- default:
- break;
- }
-
- *cancel = myCancelInterceptedProc;
- }
-
-
- /////
- //
- // SpeakNameOfNode
- // A sample node-entering procedure; we just welcome the user to the new node.
- //
- /////
-
- pascal OSErr SpeakNameOfNode (QTVRInstance theInstance, long nodeID, SInt32 refCon)
- {
- #pragma unused(refCon)
-
- Str255 myString;
- OSErr myErr = noErr;
-
- myErr = QTVRUtils_GetNodeName(theInstance, nodeID, &myString[0]);
- if (!myErr) {
- SpeakString("\p[[emph +]]Welcome [[emph -]] two");
- while (SpeechBusy())
- ;
- SpeakString(myString);
- }
-
- return(myErr);
- }
-
-